import { createVNode, render } from 'vue'
import type { App } from 'vue'
import { hsEval, HsThrottle } from '@/utils/func'
// import loadImage from "../assets/images/bg-fill.png"
import loadImage from '@/assets/img/sim-tool/bg_image.png'
// import HsLoading from '@/components/Common/HsLoading.vue'

// 获取元素的相关CSS
function getStyle(el: any, attr: any) {
  return el.currentStyle
    ? el.currentStyle[attr]
    : window.getComputedStyle(el, null)[attr]
}

// 指令 - 点击空白区域, 执行 binding 回调
const clickOutside = {
  mounted(el: any, binding: any, vnode: any, preVnode: any) {
    // 点击除绑定指令的元素之外的空白区域
    function clickOutsideHandle(e: any) {
      if (el.contains(e.target)) {
        return false
      }
      if (typeof binding.value === 'function') {
        binding.value(e)
      }
      // console.log(binding.value);
    }
    // 给当前元素绑定个私有变量, 方便取消事件监听
    el.__vueClickOutside__ = clickOutsideHandle

    document.addEventListener('click', clickOutsideHandle)
  },
  beforeUnmount(el: any) {
    document.removeEventListener('click', el.__vueClickOutside__)
    delete el.__vueClickOutside__
  },
}
// 指令 - 弹窗拖拽事件
const modalDrag = {
  mounted(el: any, binding: any, vnode: any, preVnode: any) {
    const isModalDraggable = binding.value
    const dialogHeaderEl = el.querySelector('.hs-modal-header')
    const dragDom = el.querySelector('.hs-modal-wrapper')
    /* 获取原有属性
      ie dom元素.currentStyle
      火狐谷歌 window.getComputedStyle(dom元素, null)
    */
    const sty = (function () {
      const doc: any = window.document
      if (doc?.currentStyle) {
        return (dom: any, attr: any) => dom.currentStyle[attr]
      } else {
        return (dom: any, attr: any) => getComputedStyle(dom, null)[attr]
      }
    })()

    function mousedownHandle(e: any) {
      // 鼠标按下，计算当前元素距离可视区的距离
      // 鼠标按下，计算当前元素距离可视区的距离
      const disX = e.clientX - dialogHeaderEl.offsetLeft
      const disY = e.clientY - dialogHeaderEl.offsetTop

      const screenWidth = document.body.clientWidth // body当前宽度
      const screenHeight = document.documentElement.clientHeight // 可见区域高度(应为body高度，可某些环境下无法获取)

      const dragDomWidth = dragDom.offsetWidth // 对话框宽度
      const dragDomheight = dragDom.offsetHeight // 对话框高度

      const minDragDomLeft = dragDom.offsetLeft
      const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth

      const minDragDomTop = dragDom.offsetTop
      const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight

      // 获取到的值带px 正则匹配替换
      let styL = sty(dragDom, 'left')
      let styT = sty(dragDom, 'top')

      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
      if (styL.includes('%')) {
        styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100)
        styT = +document.body.clientHeight * (+styT.replace(/%/g, '') / 100)
      } else {
        styL = +styL.replace(/\px/g, '')
        styT = +styT.replace(/\px/g, '')
      }
      document.onmousemove = function (e: any) {
        // 通过事件委托，计算移动的距离
        let left = e.clientX - disX
        let top = e.clientY - disY

        // 边界处理
        if (-left > minDragDomLeft) {
          left = -minDragDomLeft
        } else if (left > maxDragDomLeft) {
          left = maxDragDomLeft
        }

        if (-top > minDragDomTop) {
          top = -minDragDomTop
        } else if (top > maxDragDomTop) {
          top = maxDragDomTop
        }

        // 移动当前元素
        dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
      }
      document.onmouseout = function (e) {
        document.onmousemove = null
        document.onmouseup = null
        document.onmouseout = null
      }
      document.onmouseup = function (e) {
        document.onmousemove = null
        document.onmouseup = null
        document.onmouseout = null
      }
    }

    if (isModalDraggable) {
      dialogHeaderEl.style.cssText += ';cursor:move;'
      dragDom.style.cssText += ''
      el.__vueModalDragEl__ = dialogHeaderEl
      el.__vueModalDrag__ = mousedownHandle
      el.__vueModalDragEl__.addEventListener('mousedown', el.__vueModalDrag__)
    }
  },
  beforeUnmount(el: any) {
    el.__vueModalDragEl__?.removeEventListener('mousedown', el.__vueModalDrag__)
    delete el.__vueModalDrag__
  },
}

// 指令 - 点击复制, 案例: v-clickCopy:[success]="color"
// [success] 动态参数, 绑定组件的 success 函数 传递到 binding.arg
const hsCopy = {
  beforeMount(el: any, binding: any) {
    console.log('....copy', binding, el.innerHTML)
    el.targetContent = binding.value || el.innerHTML || ''
    const succssCallback = binding.arg
    function copyHandle() {
      if (!el.targetContent) return console.warn('没有需要复制的目标内容')
      // 创建textarea标签, 设置相关属性, 插入到页面并选中
      const textarea = document.createElement('textarea')
      textarea.readOnly = true
      textarea.style.position = 'fixed'
      textarea.style.top = '-99999px'
      textarea.value = el.targetContent
      document.body.appendChild(textarea)
      textarea.select()
      // 把目标内容复制进剪贴板, 该API会返回一个Boolean
      const res = document.execCommand('Copy')
      if (res) {
        console.log('复制成功，剪贴板内容：', el.targetContent)
        if (succssCallback && typeof succssCallback === 'function') {
          succssCallback(el.targetContent)
        }
      }
      // 移除textarea标签
      document.body.removeChild(textarea)
    }
    el.__HsVueCopy__ = copyHandle
    el.addEventListener('click', el.__HsVueCopy__)
  },
  updated(el: any, binding: any) {
    // 实时更新最新的目标内容
    el.targetContent = binding.value
  },
  beforeUnmount(el: any) {
    el.removeEventListener('click', el.__HsVueCopy__)
    delete el.__HsVueCopy__
  },
}

// 指令 - 拖拽元素
const hsDraggable = {
  mounted(el: any, binding: any) {
    el.style.cursor = 'move'
    // el.style.position = 'fixed'
    const containerId = binding.arg || null
    let containerWidth = window.innerWidth
    let containerHeight = window.innerHeight
    // 存在父级容器
    if (containerId) {
      const containerEle = document.getElementById(containerId) as HTMLElement
      const { width, height } = containerEle.getBoundingClientRect()
      containerWidth = width
      containerHeight = height
      if (
        !['fixed', 'absolute', 'relative'].includes(
          getStyle(containerEle, 'position'),
        )
      ) {
        containerEle.style.position = 'relative'
      }
      el.style.position = 'absolute'
    }
    function mousedown(e: MouseEvent) {
      const { width, height } = el.getBoundingClientRect()
      // 当前目标元素的left与top
      const left = el.offsetLeft
      const top = el.offsetTop
      // 保存按下的鼠标的 X 与 Y
      const mouseX = e.clientX
      const mouseY = e.clientY
      // 计算边界值
      const leftLimit = left
      const rightLimit = containerWidth - left - width
      const topLimit = top
      const bottomLimit = containerHeight - top - height
      // 监听鼠标移动
      document.onmousemove = (e) => {
        // 鼠标移动的距离
        const disX = e.clientX - mouseX
        const disY = e.clientY - mouseY
        // 左右边界
        if (disX < 0 && disX <= -leftLimit) {
          el.style.left = left - leftLimit + 'px'
        } else if (disX > 0 && disX >= rightLimit) {
          el.style.left = left + rightLimit + 'px'
        } else {
          el.style.left = left + disX + 'px'
        }
        // 上下边界
        if (disY < 0 && disY <= -topLimit) {
          el.style.top = top - topLimit + 'px'
        } else if (disY > 0 && disY >= bottomLimit) {
          el.style.top = top + bottomLimit + 'px'
        } else {
          el.style.top = top + disY + 'px'
        }
        return false
      }
      // 监听鼠标抬起
      document.onmouseup = () => {
        document.onmousemove = null
        document.onmouseup = null
      }
    }
    el.__HsVueDraggable__ = mousedown
    el.addEventListener('mousedown', el.__HsVueDraggable__)
  },
  beforeUnmount(el: any) {
    el.removeEventListener('click', el.__HsVueDraggable__)
    delete el.__HsVueDraggable__
  },
}

function imageTsExist(url: string) {
  return new Promise((resolve) => {
    let img: any = new Image()
    img.src = url
    img.onload = () => {
      if (img.complete) {
        resolve(true)
        img = null
      }
    }
    img.onerror = () => {
      resolve(false)
      img = null
    }
  })
}
// 指令 - 图片加载判断, 新增无法加载时展示图片
const hsRealImg = {
  async beforeMount(el: any, binding: any) {
    const imgURL = binding.value
    el.setAttribute('src', loadImage)
    if (imgURL) {
      const exist = await imageTsExist(imgURL)
      if (exist) {
        el.setAttribute('src', imgURL)
        return
      }
    }
  },
}

interface IHTMLElement extends HTMLElement {
  __hsVueImgSrc__?: string
  __hsVueImgIsIntersecting__?: string
  __hsVueEventHandler__?: IntersectionObserver
}
// 图片加载过一次后就移除监听
function removeListen(el: any) {
  try {
    IntersectionObserver
      ? el.__hsVueEventHandler__.disconnect()
      : window.removeEventListener('scroll', el.__hsVueEventHandler__)
  } catch {
    /* empty */
  }
  delete el.__hsVueImgSrc__
  delete el.__hsVueImgIsIntersecting__
  delete el.__hsVueEventHandler__
}
function isSaveSrc(el: any) {
  const curSrc = el.getAttribute('src')
  const realSrc = el.__hsVueImgSrc__
  return curSrc === realSrc
}
function updateImgSrc(el: any) {
  // 当src变更的时候再去更新
  const isSameSrc = isSaveSrc(el)
  // 通过isIntersecting判断是否在可视区域内
  const isIntersecting = el.__hsVueImgIsIntersecting__
  if (isIntersecting && !isSameSrc) {
    el.setAttribute('src', el.__hsVueImgSrc__)
    removeListen(el)
  }
}
function ioEvent(el: any) {
  // IntersectionObserver, 判断当前图片是否到了可视区域
  // https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
  const io = new IntersectionObserver((enties: any) => {
    const isIntersecting = enties[0].isIntersecting
    el.__hsVueImgIsIntersecting__ = isIntersecting
    // console.log(".......ioEvent", isIntersecting)
    updateImgSrc(el)
  })
  // 监听目标对象
  io.observe(el)
  el.__hsVueEventHandler__ = io
}
// 获取视口高度, 兼容不同浏览器厂商
function getClientHeight() {
  const dClientHeight = document.documentElement.clientHeight
  const bodyClientHeight = document.body.clientHeight
  let clientHeight = 0
  if (bodyClientHeight && dClientHeight) {
    clientHeight =
      bodyClientHeight < dClientHeight ? bodyClientHeight : dClientHeight
  } else {
    clientHeight =
      bodyClientHeight > dClientHeight ? bodyClientHeight : dClientHeight
  }
  return clientHeight
}
// 这里我们会借助 getBoundingClientRect() API来完成，它的兼容性就是杠杠的了，
// 而通过getBoundingClientRect()方法我们能获取到元素距离屏幕顶部的距离(top) 与 元素距离屏幕底部的距离(bottom)。
// 当 top < 当前视口高度 && bottom > 0 条件成立就能判断元素进入视口可见区域了
// 而获取视口高度可以通过 documentElement.clientHeight 和 body.clientHeight

function loadImgHandle(el: any) {
  const isSameSrc = isSaveSrc(el)
  // console.log(".......loadImgHandle", isSameSrc)
  if (!isSameSrc) {
    const clientHeight = getClientHeight()
    const { top, bottom } = el.getBoundingClientRect()
    const isIntersecting = top < clientHeight && bottom > 0
    el.__hsVueImgIsIntersecting__ = isIntersecting
    updateImgSrc(el)
  }
}
function scrollEvent(el: any) {
  loadImgHandle(el)
  const handler = HsThrottle(loadImgHandle.bind(null, el), 1000)
  el.__hsVueEventHandler__ = handler
  window.addEventListener('scroll', el.__hsVueEventHandler__)
}
// 指令 - 图片懒加载
const hsLazyImg = {
  beforeMount(el: any, binding: any) {
    const imgURL = binding.value
    el.__hsVueImgSrc__ = imgURL || ''
    // 设置loading状态图片
    el.setAttribute('src', loadImage)
  },
  mounted(el: any) {
    IntersectionObserver ? ioEvent(el) : scrollEvent(el)
  },
  updated(el: any, binding: any) {
    el.__hsVueImgSrc__ = binding.value || ''
    IntersectionObserver ? updateImgSrc(el) : loadImgHandle(el)
  },
  unmounted(el: any) {
    removeListen(el)
  },
}

// 指令 - 长按
const hsLongpress = {
  beforeMount(el: any, binding: any) {
    console.log('.......hsLongPress', binding)
    const callback = binding.value
    el.__hsVueLongPressDuration__ = binding.arg || 2000
    if (typeof callback !== 'function') {
      return console.log('v-longpress指令必须接收一个回调函数')
    }
    let timer: any = null
    const addHandler = HsThrottle(
      (e: any) => {
        if (e.type === 'click' && e.button !== 0) return
        e.preventDefault()
        if (timer === null) {
          timer = setTimeout(() => {
            callback()
            timer = null
          }, el.__hsVueLongPressDuration__)
        }
      },
      1000,
      { leading: true },
    )
    const cancelHandler = HsThrottle(
      (e: any) => {
        if (timer !== null) {
          clearTimeout(timer)
          timer = null
        }
      },
      1000,
      { leading: true },
    )
    el.__hsVueLongPressAddHandler__ = addHandler
    el.__hsVueLongPressCancelHandler__ = cancelHandler
    // 添加计时器
    el.addEventListener('mousedown', addHandler)
    el.addEventListener('touchstart', addHandler)
    // 取消计时器
    el.addEventListener('click', cancelHandler)
    el.addEventListener('mouseout', cancelHandler)
    el.addEventListener('touchend', cancelHandler)
    el.addEventListener('touchcancel', cancelHandler)
  },
  updated(el: any, binding: any) {
    el.__hsVueLongPressDuration__ = binding.arg || 2000
  },
  unmounted(el: any) {
    el.removeEventListener('mousedown', el.__hsVueLongPressAddHandler__)
    el.removeEventListener('touchstart', el.__hsVueLongPressAddHandler__)
    el.removeEventListener('click', el.__hsVueLongPressCancelHandler__)
    el.removeEventListener('mouseout', el.__hsVueLongPressCancelHandler__)
    el.removeEventListener('touchend', el.__hsVueLongPressCancelHandler__)
    el.removeEventListener('touchcancel', el.__hsVueLongPressCancelHandler__)
    delete el.__hsVueLongPressDuration__
  },
}

function htmlEventTrigger(el: any, type: string) {
  const event = new CustomEvent(type, { bubbles: false, cancelable: true })
  el.dispatchEvent(event)
}
// 指令 - input 规则校验, 限制输入内容类型
const hsInputTypes = ['custom', 'number', 'decimal', 'decimal_2']
const hsInputType = {
  mounted(el: any, binding: any) {
    const _type = binding.arg
    const bindValue = binding.value
    if (!_type && !hsInputTypes.includes(_type)) {
      return console.warn(
        '使用v-hsInputType 指令需要选定类型,如: ',
        hsInputTypes.join('/'),
      )
    }
    el.__hsVueInputTypeHandler__ = (el: any) => {
      const value = el.value
      console.log('....格式化', value)
      let reg = null,
        newValue = value,
        _rule = null
      switch (_type) {
        case 'custom':
          _rule = el.dataset.rule
          // 字符串正则转正则表达式
          if (!_rule) {
            return console.warn(
              '使用v-hsInputType:custom 需要指定 data-rule=[xxx]',
            )
          }
          _rule = hsEval(el.dataset.rule)
          el.value = newValue.replace(_rule, '')
          break
        case 'number':
          reg = /[^0-9]/
          if (reg.test(newValue)) {
            // 清除数字以外的字符
            newValue = newValue.replace(reg, '')
            console.log('...newValue', newValue)
            el.value = newValue
          }
          break
        case 'decimal':
          reg = /[^0-9.]/g
          if (reg.test(newValue)) {
            // 清除数字和'.'以外的字符
            newValue = newValue.replace(reg, '')
          } else {
            // 连续的两个'.', 只保留第一个'.'
            newValue = newValue.replace(/\.{2,}/g, '.')
            // 间隔的两个'.', 也只保留一个'.'
            newValue = newValue
              .replace('.', '$#$')
              .replace(/\./g, '')
              .replace('$#$', '.')
            // 保证不会出现重复的: 00, 01, 02 ...
            value.indexOf('.') < 0 &&
              newValue !== '' &&
              (newValue = parseFloat(newValue))
            // 第一位不能是.
            value.indexOf('.') > -1 && newValue.length === 1 && (newValue = '')
          }
          el.value = newValue
          break
        case 'decimal_2':
          reg = /[^0-9.]/g
          if (reg.test(newValue)) {
            // 清除数字和'.'以外的字符
            newValue = newValue.replace(reg, '')
          } else {
            // 连续的两个'.', 只保留第一个'.'
            newValue = newValue.replace(/\.{2,}/g, '.')
            // 间隔的两个'.', 也只保留一个'.'
            newValue = newValue
              .replace('.', '$#$')
              .replace(/\./g, '')
              .replace('$#$', '.')
            // 只能输入两位小数, 最后的.表示匹配除了换行符之外的任何单个字符, *匹配前一个字符0次或多次
            newValue = newValue.replace(
              /^(-)*([0-9]+)\.([0-9][0-9]).*$/,
              '$1$2.$3',
            )
            // 保证不会出现重复的: 00, 01, 02 ...
            value.indexOf('.') < 0 &&
              newValue !== '' &&
              (newValue = parseFloat(newValue))
            // 第一位不能是.
            value.indexOf('.') > -1 && newValue.length === 1 && (newValue = '')
          }
          el.value = newValue
          break
        default:
          break
      }
      // 记录上一次的value, 防止父组件以及子节点更新导致的updated钩子频繁触发
      el.__hsVueInputTypeOldValue__ = newValue
      htmlEventTrigger(el, 'input')
    }
    el.__hsVueInputTypeHandler__(el)
  },
  // updated 钩子在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
  updated(el: any, binding: any) {
    const value = el.value
    if (value !== el.__hsVueInputTypeOldValue__) {
      el.__hsVueInputTypeHandler__ && el.__hsVueInputTypeHandler__(el)
    }
  },
  unmounted(el: any, binding: any) {
    delete el.__hsVueInputTypeOldValue__
    delete el.__hsVueInputTypeHandler__
  },
}

// 指令 - 权限校验
// 作用: 目的主要是为了回避通过 v-show/v-if 来进来繁琐冗余的权限判断操作
const ownPermission = ['admin', 'user', 'order']
function permissionHandler(el: any, permission: any) {
  if (permission && !ownPermission.includes(permission)) {
    el.parentNode && el.parentNode.removeChild(el)
  }
}
const hsPermission = {
  mounted(el: any, binding: any) {
    permissionHandler(el, binding.value)
  },
  updated(el: any, binding: any) {
    permissionHandler(el, binding.value)
  },
}

// 指令 - loading
// 插入loading Dom
function insertDom(el: any, loadingDom: any, binding: any) {
  const isComponent = el.dataset.component !== undefined
  if (!isComponent) {
    const styles: any = {
      position: 'absolute',
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      'z-index': 1000,
      'background-color': 'rgba(0, 0, 0, 0.1)',
      display: 'flex',
      'justify-content': 'center',
      'align-items': 'center',
      'flex-direction': 'column',
    }
    if (binding.arg) {
      Object.assign(styles, binding.arg)
    }
    Object.keys(styles).forEach((property) => {
      loadingDom.style[property] = styles[property]
    })
  }
  // 给父级添加relative
  if (!['fixed', 'absolute', 'relative'].includes(getStyle(el, 'position'))) {
    el.style.position = 'relative'
  }
  // 插入dom
  el.appendChild(loadingDom)
  // 表明dom已插入
  el.__hsVueLoadingDomInserted__ = true
}
// 控制loading的显示与隐藏
function toggleLoading(el: any, binding: any) {
  if (binding.value) {
    insertDom(el, el.__hsVueLoading__, binding)
  } else {
    el.__hsVueLoading__.parentNode &&
      el.__hsVueLoading__.parentNode.removeChild(el.__hsVueLoading__)
  }
}
// function useVueLoadingComponent (el:any) {
//   const vnode = createVNode(HsLoading, {
//     icon: true
//   })
//   render(vnode, el)
//   return vnode.el
// }
function useHtmlElement(el: any) {
  const text = el.getAttribute('loading-text')
  const container = document.createElement('div')
  container.innerText = text || '加载中...'
  return container
}
// const hsLoading = {
//   beforeMount (el:any, binding:any) {
//     // console.log('.....hsLoading', binding)
//     const isComponent = el.dataset.component !== undefined
//     const loadingDom = isComponent ? useVueLoadingComponent(el) : useHtmlElement(el)
//     el.__hsVueLoading__ = loadingDom
//     if (binding.value) {
//       toggleLoading(el, binding);
//     }
//   },
//   updated (el:any, binding:any) {
//     if (binding.oldValue !== binding.value) {
//       toggleLoading(el, binding)
//     }
//   },
//   unmounted (el:any, binding:any) {
//     el.__hsVueLoadingDomInserted__ && toggleLoading(el, { value: false, modifiers: binding.modifiers });
//     el.__hsVueLoading__ && (el.__hsVueLoading__ = null);
//   }
// }

export const globalDirectives = {
  clickOutside,
  modalDrag,
  hsCopy,
  hsDraggable,
  hsRealImg,
  hsLazyImg,
  hsLongpress,
  hsInputType,
  hsPermission,
  // hsLoading,
}

/**
 * 注册全局自定义指令
 * @param app
 */
export function setupDirectives(app: App) {
  // 权限控制指令（演示）
  app.directive('clickOutside', clickOutside)
  app.directive('modalDrag', modalDrag)
  app.directive('hsDraggable', hsDraggable)
  app.directive('hsCopy', hsCopy)
  app.directive('hsRealImg', hsRealImg)
  app.directive('hsLazyImg', hsLazyImg)
  app.directive('hsLongpress', hsLongpress)
  app.directive('hsInputType', hsInputType)
  app.directive('hsPermission', hsPermission)
  // app.directive('hsLoading', hsLoading)
}
